package wechat

import (
	"context"
	"fmt"

	"code.gitea.io/gitea/models/db"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
)

// ErrFailedToOperateWechatOfficialAccountUserDB 错误类型：打开数据库失败
type ErrFailedToOperateWechatOfficialAccountUserDB struct {
	Action  string
	Message string
}

func (err ErrFailedToOperateWechatOfficialAccountUserDB) Error() string {
	return fmt.Sprintf("Failed to %v in WeChat Official Account DB: %v", err.Action, err.Message)
}

// ErrWechatOfficialAccountUserNotExist 错误类型：找不到对应的微信公众号用户
type ErrWechatOfficialAccountUserNotExist struct {
	AppID  string
	OpenID string
}

func (err ErrWechatOfficialAccountUserNotExist) Error() string {
	return fmt.Sprintf("WeChat Official Account User not found: AppID = %v, OpenID = %v", err.AppID, err.OpenID)
}

// UserWechatOpenid 用户ID与微信公众号openid一对一关联表
// 数据库指定schema下 必须存在 `user_wechat_openid`，否则报错 ERROR 1146 (42S02): Table does not exist
//
//	遵循gonic规则映射数据库表 `user_wechat_openid`，各字段注解见 https://xorm.io/docs/chapter-02/4.columns/
type UserWechatOpenid struct {
	Id          int64  `xorm:"BIGINT pk NOT NULL autoincr                                      'id'                     comment('主键')"`
	Uid         int64  `xorm:"BIGINT UNIQUE NOT NULL                                           'uid'                    comment('user表主键')"`
	WechatAppid string `xorm:"VARCHAR(50) charset=utf8mb4 collate=utf8mb4_bin NOT NULL         'official_account_appid' comment('微信公众号关联的AppID，便于区分不同公众号用户、便于后期公众号迁移')"`
	Openid      string `xorm:"VARCHAR(50) charset=utf8mb4 collate=utf8mb4_bin UNIQUE NOT NULL  'openid'                 comment('微信公众号粉丝OpenID')"`
}

// Use `charset=utf8mb4 collate=utf8mb4_bin` to fix log.Error: There are 2 table columns(`official_account_appid`, `openid`) using inconsistent collation, they should use "utf8mb4_bin". Please go to admin panel Self Check page

func init() {
	db.RegisterModel(new(UserWechatOpenid))
}

// QueryUserByOpenid 根据微信公众号用户openid查询 `user` 表用户
func QueryUserByOpenid(ctx context.Context, openid string) (*user_model.User, error) {

	// 1. 根据公众号 AppID 和 OpenID 查询数据库中 user表 主键ID
	wechatUser := &UserWechatOpenid{
		WechatAppid: setting.Wechat.UserConfig.AppID,
		Openid:      openid,
	}

	var user *user_model.User

	// 2. 开启数据库事务，查询用户信息
	err := db.WithTx(ctx, func(ctx context.Context) error {
		// 2.1 根据 `user_wechat_openid` 表 查询用户 openid
		exist, err := db.GetEngine(ctx).Get(wechatUser) // 注： 断点必须打在操作数据库之后，否则超时导致报错:  context canceled
		if err != nil {
			return ErrFailedToOperateWechatOfficialAccountUserDB{
				Action:  fmt.Sprintf("Find WeChat openid '%s'", openid),
				Message: err.Error(),
			}
		}

		if !exist {
			return ErrWechatOfficialAccountUserNotExist{
				AppID:  wechatUser.WechatAppid,
				OpenID: wechatUser.Openid,
			}
		}

		// 2.2 根据 `user` 表 主键ID查询用户信息
		user, err = user_model.GetUserByID(ctx, wechatUser.Uid)
		if err != nil {
			return err
		}
		// 数据库事务完成，返回nil自动进行Commit
		return nil
	})

	if err != nil {
		return nil, err
	}

	// 3. 检查用户是否被禁用，参考校验逻辑 services/auth/signin.go#UserSignIn.
	if user.ProhibitLogin {
		return nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name}
	}

	return user, err
}

// UpdateOrCreateWechatUser 更新/新建用户微信
func UpdateOrCreateWechatUser(ctx context.Context, user *user_model.User, openid string) error {

	// 1. 构造查询条件：DevStar 用户ID，即 UID
	wechatUser := &UserWechatOpenid{
		Uid: user.ID,
	}

	WechatAppid := setting.Wechat.UserConfig.AppID

	// 2. 开启数据库事务，保证数据库表 `user_wechat_openid` 原子操作（出错时候自动进行 transaction Rollback）
	err := db.WithTx(ctx, func(ctx context.Context) error {
		// 2.1 根据 UID 查询数据库
		has, err := db.GetEngine(ctx).Get(wechatUser)
		if err != nil {
			return ErrFailedToOperateWechatOfficialAccountUserDB{
				Action:  fmt.Sprintf("Find UserID %v", user.ID),
				Message: err.Error(),
			}
		}

		// 2.2 数据库更新或插入新记录
		if has && wechatUser.WechatAppid == WechatAppid && wechatUser.Openid == openid {
			// 有用户记录，且未发生变化，直接返回，结束事务
			return nil
		} else if has && (wechatUser.WechatAppid != WechatAppid || wechatUser.Openid != openid) {
			// 有用户记录，但公众号主体 AppId 或者 关联的用户号 OpenID 发生变化
			wechatUser.WechatAppid = WechatAppid
			wechatUser.Openid = openid
			_, err = db.GetEngine(ctx).ID(wechatUser.Id).Update(wechatUser)
			if err != nil {
				return ErrFailedToOperateWechatOfficialAccountUserDB{
					Action:  fmt.Sprintf("Update existing UID %v", user.ID),
					Message: err.Error(),
				}
			}
		} else {
			// 没有用户记录，需要插入新纪录
			wechatUser.WechatAppid = WechatAppid
			wechatUser.Openid = openid
			_, err = db.GetEngine(ctx).Insert(wechatUser)
			if err != nil {
				return ErrFailedToOperateWechatOfficialAccountUserDB{
					Action:  fmt.Sprintf("Insert new user UID = %v", user.ID),
					Message: err.Error(),
				}
			}
		}
		return nil
	})

	if err != nil {
		return ErrFailedToOperateWechatOfficialAccountUserDB{
			Action:  "Start an DB transaction",
			Message: err.Error(),
		}
	}

	log.Info("微信公众号绑定成功: (UID, User.Name, OpenID) = (%v, %v, %v)", user.ID, user.Name, openid)
	return nil
}

// DeleteWechatUser 清空当前用户的微信公众号绑定信息
func DeleteWechatUser(ctx context.Context, user *user_model.User) error {

	// 0. 检查参数
	if user == nil || user.ID <= 0 {
		return ErrFailedToOperateWechatOfficialAccountUserDB{
			Action:  "Find User",
			Message: "User and its ID cannot be nil",
		}
	}

	// 1. 构造查询条件：DevStar 用户ID，即 UID
	wechatUser := &UserWechatOpenid{
		Uid: user.ID,
	}

	// 2. 开启数据库事务，删除数据库表 `user_wechat_openid` 中 user.ID 对应条目
	err := db.WithTx(ctx, func(ctx context.Context) error {
		// 2.1 根据 UID 查询数据库
		_, err := db.GetEngine(ctx).Delete(wechatUser)
		if err != nil {
			return ErrFailedToOperateWechatOfficialAccountUserDB{
				Action:  fmt.Sprintf("Delete WeChat Official Account Binding for User '%v'(ID = %v)", user.Name, user.ID),
				Message: err.Error(),
			}
		}
		return nil
	})

	return err
}
